<?php

declare(strict_types=1);

namespace Zms\Validate\Rule;

use Zms\Exception\ValidateException;
use Zms\Unit\Is;

/**
 * 元素是否在数组中验证
 * 验证输入的数组值是否与预定义的枚举数组有交集。
 * 支持严格模式：严格模式下，输入数组中的所有值都必须存在于枚举数组中；非严格模式下，仅过滤无效值。
 */
class ListIntersectValidate extends RuleAbstract
{
    /**
     * 枚举数据源
     * 存储允许的值列表。
     * @var array
     */
    protected $_enums = [];

    /**
     * 错误提示
     * 当验证失败时返回的错误信息模板。
     * @var string
     */
    protected $_error = '{label}数据错误';

    /**
     * 严格模式
     * false：过滤无效值，只保留有效的值。
     * true：输入数组中的所有值都必须存在于枚举数组中，否则验证失败。
     * @var bool
     */
    protected $_strict = false;

    /**
     * 设置枚举数据
     * 枚举数据可以是数组或通过回调函数动态生成的数组。
     * @param array|callable $enums 枚举数据或生成枚举数据的回调函数
     * @return $this
     * @throws ValidateException 如果枚举数据无效时抛出异常
     */
    public function setEnums($enums): ListIntersectValidate
    {
        // 如果是回调函数，调用回调获取枚举数据
        if (is_callable($enums)) {
            $enum_list = $enums();
        } else {
            $enum_list = $enums;
        }

        // 验证枚举数据是否为有效数组
        if (Is::list($enum_list)) {
            $this->_enums = $enum_list;
            return $this;
        }

        // 如果枚举数据无效，抛出异常
        throw new ValidateException('验证参数enums错误');
    }

    /**
     * 设置严格模式
     * @param bool $strict 是否启用严格模式
     * @return $this
     */
    public function setStrict(bool $strict): ListIntersectValidate
    {
        $this->_strict = $strict;
        return $this;
    }

    /**
     * 验证输入数组是否与枚举数组有交集
     * @param mixed $value 输入值（必须是数组）
     * @param string $label 验证名（用于错误信息中）
     * @return bool 验证结果
     */
    public function validate($value, string $label = ''): bool
    {
        // 确保输入值是数组
        if (Is::list($value)) {
            // 如果值为空且 `_noSkipping` 为 true，则直接返回 true
            if (Is::empty($value) && $this->_noSkipping === true) {
                $this->_value = $value;
                return true;
            }

            // 初始化结果数组和错误计数
            $new = [];
            $err = 0;

            // 遍历输入数组，验证每个值是否存在于枚举数组中
            foreach ($value as $val) {
                if (in_array($val, $this->_enums)) {
                    $new[] = $val; // 保留有效值
                } else {
                    $err++; // 记录无效值
                }
            }

            // 如果启用严格模式且存在无效值，则验证失败
            if ($this->_strict === true && $err > 0) {
                return $this->addError($this->_error, $label);
            }

            // 保存过滤后的结果
            $this->_value = $new;
            return true;
        }

        // 如果输入值不是数组，验证失败
        return $this->addError($this->_error, $label);
    }
}
